package com.ebeitech.auction.db.dao;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.ebeitech.auction.db.DBOperateSQLiteHelper;
import com.ebeitech.auction.db.DBSQLiteOpenHelper;
import com.ebeitech.auction.db.annotation.Column;
import com.ebeitech.auction.db.annotation.Id;
import com.ebeitech.auction.db.annotation.PrimaryKey;
import com.ebeitech.auction.db.annotation.Table;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by alice on 2017/10/24.
 */
public class BaseDBDaoImpl <T> implements BaseDBDao<T>{

	protected String TAG;
	protected DBSQLiteOpenHelper dbHelper;

	protected SQLiteDatabase db = null;

	protected static final ReentrantLock lock = new ReentrantLock();

	protected Class<T> clazz;

	protected String tableName;

	protected List<Method> allMethods;

	protected String primaryKey;

	protected Method primaryKeyMethod;

	/** The Constant METHOD_INSERT. */
	protected static final int METHOD_INSERT = 0;

	/** The Constant METHOD_UPDATE. */
	protected static final int METHOD_UPDATE = 1;


	public BaseDBDaoImpl(DBSQLiteOpenHelper dbHelper,Class<T> clazz){
		this.dbHelper = dbHelper;
		if(clazz == null){
			this.clazz = ((Class<T>)((java.lang.reflect.ParameterizedType) super.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
		}else{
			this.clazz = clazz;
		}
		TAG = clazz.getName();

		if(this.clazz.isAnnotationPresent(Table.class)){
			Table table = (Table) this.clazz.getAnnotation(Table.class);
			this.tableName = table.name();
		}

		this.allMethods = DBOperateSQLiteHelper.joinMethods(this.clazz.getDeclaredMethods());

		for(Method method : allMethods){
			if(method.isAnnotationPresent(PrimaryKey.class)){
				primaryKeyMethod = method;
				primaryKey = ((Column) method.getAnnotation(Column.class)).name();
				break;
			}
		}
		checkDBOpened();
	}

	/**
	 * 插入一条数据
	 * @param entity
	 * @return
	 */
	@Override
	public long insert(T entity) {
		if(entity == null){
			return -1;
		}
		checkDBOpened();
		long rowId = -1;
		try {
			String logSql = null;
			ContentValues cv = new ContentValues();
			logSql = setContentValues(entity,cv,METHOD_INSERT);
			lock.lock();
			rowId = db.insert(this.tableName,null,cv);
			Log.i("","sql:"+logSql);
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		return rowId;
	}

	/**
	 *  插入一列数据
	 *  通过事务的方式插入
	 * @param entityList
	 * @return
	 */
	@Override
	public long[] insertList(List<T> entityList) {
		if(entityList == null){
			return new long[0];
		}
		checkDBOpened();
		long[] rowIds = new long[entityList.size()];
		try {
			lock.lock();
			if(entityList.size() > 20){

			}
			db.beginTransaction();
			for (int i = 0; i < entityList.size(); i++){
				T entity = entityList.get(i);
				ContentValues cv = new ContentValues();
				setContentValues(entity,cv,METHOD_INSERT);
				rowIds[i] = db.insert(this.tableName,null,cv);
			}
			db.setTransactionSuccessful();

		}catch (Exception e){
			e.printStackTrace();
		}finally {
			db.endTransaction();
			lock.unlock();
		}
		return rowIds;
	}

	/**
	 *
	 * @param contentValues
	 * @param whereClause
	 * @param whereArgs
	 * @return
	 */
	@Override
	public long update(ContentValues contentValues, String whereClause, String[] whereArgs) {
		checkDBOpened();
		long rowId = -1;
		try {
			lock.lock();
			rowId = db.update(tableName,contentValues,whereClause,whereArgs);
			Log.i(TAG,"sql:"+ "[update]: update " + this.tableName + " set " + contentValues.toString()
					+ " where " + whereClause + whereArgs);
		}catch (Exception e){
			e.printStackTrace();
			Log.i(TAG,"update error");
		}finally {
			lock.unlock();
		}
		return rowId;
	}


	/**
	 * 更新entity里的所有数据
	 * @param entity
	 * @param whereClause
	 * @param whereArgs
	 * @return
	 */
	@Override
	public long update(T entity,String whereClause,String[] whereArgs) {
		if(entity == null){
			return -1;
		}
		checkDBOpened();
		long rowId = -1;
		try {
			String logSql = null;
			ContentValues cv = new ContentValues();
			if(isOperationByPrimaryKey(whereClause)){
				Object value = primaryKeyMethod.invoke(entity,new Object[]{});
				whereClause = primaryKey + " = '" + String.valueOf(value) +"'";
			}
			logSql = setContentValues(entity,cv,METHOD_UPDATE);

			lock.lock();
			rowId = db.update(tableName,cv,whereClause,whereArgs);
			Log.i(TAG,"sql:"+ "[update]: update " + this.tableName + " set " + logSql
					+ " where " + whereClause + whereArgs);
		}catch (Exception e){
			e.printStackTrace();
			Log.i(TAG,"update error");
		}finally {
			lock.unlock();
		}
		return rowId;
	}


	/**
	 * 更新entity里的所有数据
	 * @param entityList
	 * @return
	 */
	@Override
	public long[] updateList(List<T> entityList) {
		if(entityList == null){
			return new long[0];
		}
		checkDBOpened();
		long[] rowIds = new long[entityList.size()];
		try {
			lock.lock();
			db.beginTransaction();
			for (int i = 0; i < entityList.size(); i++){
				T entity = entityList.get(i);
				ContentValues cv = new ContentValues();
				String whereClause = null;
				if(isOperationByPrimaryKey(whereClause)){
					Object value = primaryKeyMethod.invoke(entity,new Object[]{});
					whereClause = primaryKey + " = '" + String.valueOf(value) +"'";
				}
				setContentValues(entity,cv,METHOD_UPDATE);
				rowIds[i] = db.update(tableName,cv,whereClause,null);
			}
			db.setTransactionSuccessful();
		}catch (Exception e){
			e.printStackTrace();
			Log.i(TAG,"updateList error");
		}finally {
			db.endTransaction();
			lock.unlock();
		}

		return rowIds;
	}

	/**
	 * 更新或插入实体类
	 * 必须有primaryKey 才可以使用
	 * @param entity
	 * @return
	 */
	@Override
	public long insertOrUpdate(T entity) {
		if(entity == null){
			return -1;
		}
		String whereClause = null;
		if(isOperationByPrimaryKey(whereClause)){
			try {
				Object value = primaryKeyMethod.invoke(entity,new Object[]{});
				whereClause = primaryKey + " = '" + String.valueOf(value) +"'";
				if(queryCount(whereClause,null) > 0){
					return update(entity,whereClause,null);
				}
			}catch (Exception e){
				e.printStackTrace();
			}
		}
		return insert(entity);
	}

	@Override
	public long insertOrUpdate(T entity, String whereClause, String[] whereArgs) {
		if(entity == null){
			return -1;
		}
		try {
			if(queryCount(whereClause,whereArgs) > 0){
				return update(entity,whereClause,null);
			}
		}catch (Exception e){
			e.printStackTrace();
		}
		return insert(entity);
	}

	/**
	 * 更新或插入实体类列表
	 * 必须有primaryKey 才可以使用
	 * @param entityList
	 * @return
	 */
	@Override
	public long insertOrUpdateList(List<T> entityList) {
		if(entityList == null || entityList.isEmpty()){
			return 0;
		}
		long rowIds = 0;
		List<T> insertList = new ArrayList<T>();
		List<T> updateList = new ArrayList<T>();
		String whereClause = null;
		if(isOperationByPrimaryKey(whereClause)){
			for (T entity: entityList ) {
				try {
					Object value = primaryKeyMethod.invoke(entity,new Object[]{});
					whereClause = primaryKey + " = '" + String.valueOf(value) +"'";
				}catch (Exception e){
					e.printStackTrace();
				}
				if(queryCount(whereClause,null) > 0){
					updateList.add(entity);
				}else {
					insertList.add(entity);
				}
			}
		}else{
			insertList.addAll(entityList);
		}
		if(insertList.size() > 0){
			rowIds = insertList(insertList).length;
		}
		if(updateList.size() > 0){
			rowIds += updateList(updateList).length;
		}

		return rowIds;
	}

	/**
	 * 设置数据库要更新的值
	 *
	 * @param entity
	 * @param cv
	 * @param type 预执行的操作
	 * @return
	 * @throws InvocationTargetException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	@Override
	public String setContentValues(T entity, ContentValues cv,int type) throws InvocationTargetException,InstantiationException,IllegalAccessException{
		StringBuffer strField = new StringBuffer("(");
		StringBuffer strValue = new StringBuffer(" values(");
		StringBuffer strUpdate = new StringBuffer(" ");

		for (Method method : allMethods){
			Column column = (Column) method.getAnnotation(Column.class);
			method.setAccessible(true);
			Object value = method.invoke(entity,new Object[]{});

			if(value == null){
				continue;
			}
			if(method.isAnnotationPresent(Id.class)){
				continue;
			}
			if(type == METHOD_UPDATE && method.isAnnotationPresent(PrimaryKey.class)){
				//主键不允许更新
				continue;
			}

			cv.put(column.name(),String.valueOf(value));

			if (type == METHOD_INSERT) {
				strField.append(column.name()).append(",");
				strValue.append("'").append(value).append("',");
			} else {
				strUpdate.append(column.name()).append("=").append("'").append(
						value).append("',");
			}
		}

		if (type == METHOD_INSERT) {
			strField.deleteCharAt(strField.length() - 1).append(")");
			strValue.deleteCharAt(strValue.length() - 1).append(")");
			return strField.toString() + strValue.toString();
		} else {
			return strUpdate.deleteCharAt(strUpdate.length() - 1).append(" ").toString();
		}
	}

	/**
	 * 自定义查询
	 * 使用完需要关闭数据库
	 * @param sql
	 * @param whereArgs
	 * @return
	 */
	@Override
	public Cursor rawQuery(String sql, String[] whereArgs) {
		checkDBOpened();
		Cursor cursor = null;
		synchronized (lock){
			cursor = db.rawQuery(sql,whereArgs);
		}
		return cursor;
	}

	@Override
	public Cursor queryCursor(String[] columns, String whereClause, String[] whereArgs) {
		checkDBOpened();
		Cursor cursor = null;
		synchronized (lock){
			cursor = db.query(tableName,columns,whereClause,whereArgs,null,null,null);
		}
		return cursor;
	}

	/**
	 * 根据条件查询一条数据
	 * @param whereClause
	 * @param whereArgs
	 * @return
	 */
	@Override
	public T query(String[] columns,String whereClause, String[] whereArgs) {
		checkDBOpened();
		T entity = null;
		Cursor cursor = null;
		synchronized (lock){
			cursor = db.query(tableName,columns,whereClause,whereArgs,null,null,null);
		}
		try {
			if(cursor != null ){
				cursor.moveToFirst();
				if(!cursor.isAfterLast()){
					if(entity == null){
						entity = clazz.newInstance();
					}
					getEntityFromCursor(entity,cursor,null);
				}
			}

		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		closeCursor(cursor);
		return entity;
	}

	/**
	 * 根据条件查询数据
	 * @param columns
	 * @param whereClause
	 * @param whereArgs
	 * @return
	 */
	@Override
	public List<T> queryList(String[] columns,String whereClause, String[] whereArgs) {
		return queryList(true,columns,whereClause,whereArgs,null,null,null,null);
	}

	@Override
	public List<T> queryList(String[] columns,String whereClause, String[] whereArgs,String groupBy) {
		return queryList(true,columns,whereClause,whereArgs,groupBy,null,null,null);
	}
	/**
	 * 根据条件查询数据
	 * @param distinct
	 * @param columns
	 * @param whereClause
	 * @param whereArgs
	 * @param groupBy
	 * @param having
	 * @param orderBy
	 * @param limit
	 * @return
	 */
	@Override
	public List<T> queryList(boolean distinct,String[] columns,String whereClause, String[] whereArgs, String groupBy, String having, String orderBy, String limit) {
		checkDBOpened();
		List<T> list = new ArrayList<T>();
		Cursor cursor = null;
		synchronized (lock){
			cursor = db.query(distinct,tableName,columns,whereClause,whereArgs,groupBy,having,orderBy,limit);
		}
		try {
			getListFromCursor(list,cursor,columns);
			closeCursor(cursor);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
		return list;
	}

	/**
	 * 查询数量
	 * @param whereClause
	 * @param whereArgs
	 * @return
	 */
	@Override
	public int queryCount(String whereClause, String[] whereArgs) {
		checkDBOpened();
		int count = 0;
		synchronized (lock){
			Cursor cursor = db.query(tableName,null,whereClause,whereArgs,null,null,null);
			if(cursor != null){
				count = cursor.getCount();
			}
			closeCursor(cursor);
		}
		return count;
	}

	@Override
	public void getListFromCursor(List<T> list, Cursor cursor,String[] columns) throws IllegalAccessException, InstantiationException{
		if(cursor == null){
			Log.i(TAG,"查询的游标为空");
			return;
		}

		if(list == null) {
			list = new ArrayList<T>();
		}

		cursor.moveToFirst();
		while (!cursor.isAfterLast()){
			T entity = clazz.newInstance();
			getEntityFromCursor(entity,cursor,columns);
			list.add(entity);
			cursor.moveToNext();
		}
	}

	/**
	 * 从Cursor中获取Entity
	 * @param entity
	 * @param cursor
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	@Override
	public void getEntityFromCursor(T entity, Cursor cursor,String[] columns) throws IllegalAccessException, InstantiationException {

		if(cursor == null || cursor.getCount() <= 0 ||cursor.isAfterLast()){
			Log.i(TAG,"查询的游标为空");
			return;
		}
		if(entity == null){
			entity = clazz.newInstance();
		}
		List<String> columnNames = null;
		if(columns != null){
			columnNames = Arrays.asList(columns);
		}
		for(Method method : allMethods){
			Column column = (Column) method.getAnnotation(Column.class);
			Class<?> fieldType =  method.getReturnType();
			if(columnNames != null && !columnNames.contains(column.name())){
				continue;
			}
			String methodName = method.getName();
			String setMethodName = null;

			if("get".equals(methodName.substring(0,3))){
				setMethodName = "set" + methodName.substring(3);
			}else if("is".equals(methodName.substring(2))){
				setMethodName = "set" + methodName.substring(3);
			}else{
				Log.i(TAG,"未找到 " + methodName + " 对应的set方法");
				continue;
			}

			try {
				Method setMethod = clazz.getDeclaredMethod(setMethodName,fieldType);
				if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)){
					setMethod.invoke(entity,cursor.getInt(cursor.getColumnIndex(column.name())));
				}else {
					String value = cursor.getString(cursor.getColumnIndex(column.name()));
					if (String.class == fieldType) {
						setMethod.invoke(entity,value);
					}else if ((Long.TYPE == fieldType) || (Long.class == fieldType)){
						setMethod.invoke(entity,Long.valueOf(value));
					}else if ((Long.TYPE == fieldType) || (Long.class == fieldType)){
						setMethod.invoke(entity,Long.valueOf(value));
					}else if ((Float.TYPE == fieldType) || (Float.class == fieldType)){
						setMethod.invoke(entity,Float.valueOf(value));
					}else if ((Double.TYPE == fieldType) || (Double.class == fieldType)){
						setMethod.invoke(entity,Double.valueOf(value));
					}else if ((Boolean.TYPE == fieldType) || (Boolean.class == fieldType)){
						setMethod.invoke(entity,Boolean.valueOf(value));
					}else{
						setMethod.invoke(entity,value);
					}
				}
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
				Log.i(TAG,setMethodName + " 失败");
			} catch (InvocationTargetException e) {
				e.printStackTrace();
				Log.i(TAG,setMethodName + " 失败");
			} catch (NumberFormatException e){
				e.printStackTrace();
				Log.i(TAG,setMethodName + " 失败");
			}
		}
	}

	/**
	 * 判断更新或者查询是否需要通过PrimaryKey
	 * @param whereClause
	 * @return
	 */
	protected boolean isOperationByPrimaryKey(String whereClause){
		if((whereClause == null || whereClause.trim().length() <= 0) && primaryKey != null && primaryKey.length() > 0){
			return true;
		}
		return false;
	}

	@Override
	public long delete(String whereClause, String[] whereArgs) {
		checkDBOpened();
		long rowId = -1;
		try{
			lock.lock();
			rowId = db.delete(tableName,whereClause,whereArgs);
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		return rowId;
	}

	/**
	 * 如非特殊需要，请勿调用此方法
	 * @return
	 */
	@Override
	public long deleteAll() {
		checkDBOpened();
		long rowId = -1;
		try{
			lock.lock();
			rowId = db.delete(tableName,null,null);
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			lock.unlock();
		}

		return rowId;
	}

	/**
	 * 数据库使用前先检查数据库是否打开
	 */
	protected void checkDBOpened(){
		if(db == null || !db.isOpen() ){
			startReadableDatabase();
			//Log.e("db","先调用startReadableDatabase 初始化数据库");
		}
	}

	/**
	 * 获取读数据库，数据库操作前必须调用
	 */
	protected void startReadableDatabase(){
		try {
			lock.lock();
			if( db == null || !db.isOpen()){
				db = this.dbHelper.getReadableDatabase();
			}
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}

	public void closeCursor(Cursor cursor){
		if(cursor != null){
			cursor.close();
			cursor = null;
		}
	}

}
